/*
//
//              INTEL CORPORATION PROPRIETARY INFORMATION
//  This software is supplied under the terms of a license  agreement or
//  nondisclosure agreement with Intel Corporation and may not be copied
//  or disclosed except in  accordance  with the terms of that agreement.
//    Copyright (c) 2007-2008 Intel Corporation. All Rights Reserved.
//
//
*/

#include "umc_defs.h"
#if defined(UMC_ENABLE_AVS_VIDEO_ENCODER)

#include "ippi.h"

#include "umc_avs_enc_fusion_core.h"

#include "umc_avs_enc_pic.h"
#include "umc_avs_enc_header_init.h"
#include "umc_avs_enc_bit_stream.h"

#include "umc_automatic_mutex.h"

namespace UMC
{

static
Status CopyFrame(AVSEncFrame *pFrame, VideoData *pVideoSrc)
{
    VideoData::PlaneInfo planeInfo[3];
    ColorFormat colorFormat;
    Status umcRes;

    // check error(s)
    if ((NULL == pFrame) ||
        (NULL == pVideoSrc))
        return UMC_ERR_NULL_PTR;

    // check parameters
    colorFormat = pVideoSrc->GetColorFormat();
    if ((YUV420 != colorFormat) && (YV12 != colorFormat))
        return UMC_ERR_INVALID_PARAMS;

    try
    {
        // get planes info
        umcRes = pVideoSrc->GetPlaneInfo(planeInfo + 0, 0);
        if (UMC_OK != umcRes)
            return umcRes;
        umcRes = pVideoSrc->GetPlaneInfo(planeInfo + 1, (YUV420 == colorFormat) ? (1) : (2));
        if (UMC_OK != umcRes)
            return umcRes;
        umcRes = pVideoSrc->GetPlaneInfo(planeInfo + 2, (YUV420 == colorFormat) ? (2) : (1));
        if (UMC_OK != umcRes)
            return umcRes;

        // check dimensions
        if ((pFrame->m_sizeLuma.width != planeInfo[0].m_ippSize.width) ||
            (pFrame->m_sizeLuma.height != planeInfo[0].m_ippSize.height))
            return UMC_ERR_INVALID_PARAMS;

        // do copying
        ippiCopy_8u_C1R(planeInfo[0].m_pPlane, (Ipp32s) planeInfo[0].m_nPitch,
                        (Ipp8u *) pFrame->m_pSrcPlanes8u[0], pFrame->m_iPitch,
                        pFrame->m_sizeLuma);
        ippiCopy_8u_C1R(planeInfo[1].m_pPlane, (Ipp32s) planeInfo[1].m_nPitch,
                        (Ipp8u *) pFrame->m_pSrcPlanes8u[1], pFrame->m_iPitch,
                        pFrame->m_sizeChroma);
        ippiCopy_8u_C1R(planeInfo[2].m_pPlane, (Ipp32s) planeInfo[2].m_nPitch,
                        (Ipp8u *) pFrame->m_pSrcPlanes8u[2], pFrame->m_iPitch,
                        pFrame->m_sizeChroma);
    }
    catch(...)
    {
        umcRes = UMC_ERR_FAILED;
    }

    return umcRes;

} // Status CopyFrame(AVSEncFrame *pFrame, VideoData *pVideoSrc)

//static
Ipp32u IsThereSomethingToEncode(AVSListElement<AVSFrame> *pFrameList)
{
    Ipp32u framesToEncode = 0;

    // run over frame list
    while (pFrameList)
    {
        if (false == pFrameList->IsShown())
        {
            switch (pFrameList->m_picHeader.PictureType)
            {
            case AVS_I_PICTURE:
            case AVS_P_PICTURE:
                framesToEncode += 1;
                break;

            case AVS_B_PICTURE:
                if (pFrameList->ReferencesReady())
                    framesToEncode += 1;
                break;
            }
        }

        // get next frame
        pFrameList = pFrameList->GetNext();
    }

    return framesToEncode;

} // Ipp32u IsThereSomethingToEncode(AVSListElement<AVSFrame> *pFrameList)
static
Ipp32s GetNumberOfFreeMeFrame(Ipp32s *m_meMask)
{
    Ipp32s mask = *m_meMask;
    Ipp32s temp;
    for (Ipp32s i = 0; i < 8; i++)
    {
        temp = (mask >> i) & 1;
        if (0 == temp)
            return i;
    }
    // error, max MeFrame number is 8
    return 8;
};

Status AVSEncFusionCore::LoadSource(MediaData *pSrc)
{
    VideoData *pVideoSrc = DynamicCast<VideoData> (pSrc);
    AVSListElement<AVSFrame> *pFrame;
    Status umcRes;
    Ipp32u framesToEncode = 0;

    // check end of stream
    if (NULL == pSrc)
    {
        return UMC_OK;
    }
    // check error(s)
    if (NULL == pVideoSrc)
    {
        return UMC_ERR_NULL_PTR;
    }

    // set picture parameters
    switch (pVideoSrc->GetFrameType())
    {
    case I_PICTURE:
        umcRes = InitializeIPictureHeader(&m_picHeader, pVideoSrc);
        break;

    case P_PICTURE:
        umcRes = InitializePPictureHeader(&m_picHeader, pVideoSrc);
        break;

    case B_PICTURE:
        umcRes = InitializeBPictureHeader(&m_picHeader, pVideoSrc);
        break;

    default:
        umcRes = UMC_ERR_INVALID_PARAMS;
        break;
    }
    // here setting picture qp again //old style
    //m_picHeader.picture_qp = m_qp;
    m_picHeader.picture_qp = m_pAVSCompressorParams->m_iConstQuant;
    if (UMC_OK != umcRes)
        return umcRes;

    // allocate one more frame
    pFrame = GetFreeFrame();
    if (NULL == pFrame)
        return UMC_ERR_ALLOC;
    umcRes = AVSFusionCore::InitializeFrame(pFrame);
    if (UMC_OK != umcRes)
        return umcRes;
    // MeFrame for current AVSPicture doesn't select
    // get number of first free MeFrame
    Ipp32s meFrameNum = GetNumberOfFreeMeFrame(&m_meMask);
    // set free MeFrame to AVSPicture
    pFrame->m_pMeFrame = m_pMeFramesList[meFrameNum];
    m_meMask = m_meMask | (1 << meFrameNum);
    //if (AVS_B_PICTURE == m_picHeader.PictureType)
    //    m_meMaskBFrame = m_meMaskBFrame | (1 << meFrameNum);
    // set frame's references
    if (AVS_B_PICTURE != m_picHeader.PictureType)
    {
        umcRes = CreateReferenceLists(pFrame);
        if (UMC_OK != umcRes)
            return umcRes;
    }

    // do frame copying
    umcRes = CopyFrame((AVSEncFrame *) pFrame, pVideoSrc);
    if (UMC_OK != umcRes)
        return umcRes;

    // allocates slices & memory pieces
    umcRes = AllocateSlices(pFrame, pVideoSrc);
    if (UMC_OK != umcRes)
    {
        PurgeFrame(pFrame);
        return umcRes;
    }

    // set slice parameters
    pFrame->SetSlices();

    // then move it into the being coded frame list
    {
        AutomaticMutex guard(m_guard.ExtractHandle());

        // remove the frame from the free frame list
        pFrame = m_FreeFrames.ExtractHead();

        if (AVS_B_PICTURE == m_picHeader.PictureType)
        {
            m_Frames.AddToTail(*pFrame);
        }
        else
        {
            AVSListElement<AVSFrame> *pFrameList;

            // find the appropriate place in ready sequence
            pFrameList = m_Frames.GetHead();
            while ((pFrameList) &&
                   (pFrameList->GetNext()) &&
                   (pFrameList->GetNext()->ReferencesReady()))
            {
                pFrameList = pFrameList->GetNext();
            }
            // set the frame into ready sequence
            if (pFrameList)
            {
                pFrame->SetNext(pFrameList->GetNext());
                pFrameList->SetNext(pFrame);

                // get the next frame
                pFrameList = pFrame->GetNext();
            }
            else
            {
                m_Frames.AddToHead(*pFrame);
            }

            // correct references of previous B frames
            while (pFrameList)
            {
                if (false == pFrameList->ReferencesReady())
                {
                    CreateReferenceLists(pFrameList);
                }

                // get the next frame
                pFrameList = pFrameList->GetNext();
            }
        }

        // get frame buffer state
        framesToEncode = IsThereSomethingToEncode(m_Frames.GetHead());
    }

    // check how many MeFrames is used (and mark unused)
    CheckMeFramesList();

    if ((m_numFrames < m_maxFrames) && (1 < m_numThreads))
    {
        umcRes = UMC_ERR_NOT_ENOUGH_DATA;
    }
    else
    {
        // check number of allocated frames
        if (framesToEncode < m_numThreads)
        {
            m_maxFrames += 1;
            umcRes = UMC_ERR_NOT_ENOUGH_DATA;
        }
        else
        {
            umcRes = UMC_OK;
        }
    }

    return umcRes;

} // Status AVSEncFusionCore::LoadSource(MediaData *pSrc)

Status AVSEncFusionCore::AllocateSlices(AVSFrame *pFrame, VideoData * /* pVideoSrc */)
{
    const AVS_SEQUENCE_HEADER *pSeqHeader;
    const AVS_PICTURE_HEADER *pPicHeader;
    //AVS_SLICE_HEADER *pSlcHeader;

    AVS_BIT_STREAM_CONTEXT ctx;
    //AVS_DECODING_CONTEXT *pEncCtx;
    //AVS_RECONSTRUCTING_CONTEXT *pDisCtx;
    //AVS_DEBLOCKING_CONTEXT *pDebCtx;

    AVSListElement<AVSSlice> *pSlice;
    AVSListElement<AVSMemory> *pMemoryPiece;
    size_t sizeReq;
    //Ipp32s MbYOffset;

    // decide the default size for memory piece
    sizeReq = m_seqHeader.horizontal_size * m_seqHeader.vertical_size * 4;

    pSlice = GetFreeSlice(false);
    if (NULL == pSlice)
        return UMC_ERR_ALLOC;
    pMemoryPiece = GetFreeMemoryPiece(sizeReq);
    if (NULL == pMemoryPiece)
    {
        m_FreeSlices.AddToHead(*pSlice);
        return UMC_ERR_ALLOC;
    }

    pPicHeader = &m_picHeader;
    pSeqHeader = &m_seqHeader;

    // initialize bit stream context
    InitializeEncBitStream(&ctx, pMemoryPiece->GetBuffer(), sizeReq);

    // set pointer in the m_meCtx on the MeFrames
    memcpy(pSlice->m_meCtx.m_pMeFrame,m_pMeFramesList, sizeof(m_pMeFramesList));

    // initialize slice header
    memset(&(pSlice->m_SlcHeader), 0, sizeof(pSlice->m_SlcHeader));
    pSlice->m_SlcHeader.first_mb = 0;
    pSlice->m_SlcHeader.fixed_slice_qp = 0;/*pPicHeader->fixed_picture_qp ^ 1;*/
    pSlice->m_SlcHeader.slice_qp = 31;

    // reset decoding context & set bit stream
    memset(&(pSlice->m_decCtx), 0, sizeof(AVS_DECODING_CONTEXT));
    pSlice->m_decCtx.m_stream = ctx;

    // move elements to the frame
    pFrame->m_Slices.AddToTail(*pSlice);
    pFrame->m_MemoryPieces.AddToTail(*pMemoryPiece);

    return UMC_OK;

} // Status AVSEncFusionCore::AllocateSlices(AVSFrame *pFrame, VideoData *pVideoSrc)

Status AVSEncFusionCore::CheckMeFramesList(void)
{
    AVSListElement<AVSFrame> *pFrameList;
    Ipp32s i, tempMask = 0;
    pFrameList = m_Frames.GetHead();
    while (pFrameList)
    {
        // max alloc MeFrames is 8
        for (i = 0; i < 8; i++)
        {
            if (m_pMeFramesList[i] == pFrameList->m_pMeFrame)
            {
                tempMask = tempMask | (1 << i);
                break;
            }
        }
        pFrameList = pFrameList->GetNext();
    }
    m_meMask = tempMask;
    return UMC_OK;
} // Status AVSEncFusionCore::CheckMeFramessList(void)

} // namespace UMC

#endif // defined(UMC_ENABLE_AVS_VIDEO_ENCODER)
